using System;
using System.Collections;
using System.ComponentModel;
using System.IO;
using System.Xml.Serialization;
using Core.Graphics;
using Microsoft.Xna.Framework;
using SerializationUtilities;


namespace Core.Mapping
{
    [Serializable()]
    [DefaultPropertyAttribute("World")]
    [XmlRoot()]
    public class TileWorld
    {
        public static TileWorld CurrentWorld
        {
            get { return _CurrentWorld; }
            set { _CurrentWorld = value; }
        }

        private static TileWorld _CurrentWorld;

        [CategoryAttribute("World"), DescriptionAttribute("World Name")]
        [XmlAttribute("Name")]
        public string Name { get { return _Name; } set { _Name = value; } }
        [CategoryAttribute("World"), DescriptionAttribute("Zones Wide")]
        [XmlAttribute("ZoneGridWidth")]
        public int ZoneGridWidth { get { return _ZoneGridWidth; } }
        [CategoryAttribute("World"), DescriptionAttribute("Zones Heigh")]
        [XmlAttribute("ZoneGridHeight")]
        public int ZoneGridHeight { get { return _ZoneGridHeight; } }
        [CategoryAttribute("World"), DescriptionAttribute("World Zones")]
        [XmlArray("Zones")]
        public TileZone[] Zones { get { return _ZoneGrid; } set { _ZoneGrid = value; } }

        public string WorldFilePath = ""; [XmlIgnore()]

        public DirectoryInfo diWorld; [XmlIgnore()]
        public DirectoryInfo diZones; [XmlIgnore()]

        public DirectoryInfo diTextures; [XmlIgnore()]
        public DirectoryInfo diTerrain; [XmlIgnore()]

        public DirectoryInfo diCharacters; [XmlIgnore()]

        protected string _Name;

        protected int _ZoneGridWidth = 5;

        protected int _ZoneGridHeight = 5;

        protected TileZone[] _ZoneGrid;

        public TileWorld()
        {
            _Name = "New World";
            _ZoneGrid = new TileZone[_ZoneGridHeight * _ZoneGridWidth];
            //Set static reference for other code to reference.
            TileWorld._CurrentWorld = this;
        }

        public ArrayList GetMapsForRectangle(Rectangle currentView)
        {
            ArrayList maps = new ArrayList();
            int x = currentView.X / (128 * ZoneGridWidth);
            int y = currentView.Y / (128 * ZoneGridHeight);

            TileZone tz = _ZoneGrid[x + y * ZoneGridWidth];
            //Load first set of maps
            if (tz != null)
            {
                maps.AddRange(tz.GetMapsForRectangle(currentView));

                if (currentView.Right >= tz.WorldCoordinate.X + tz.ZoneWidth)
                {
                    if (_ZoneGrid[x + 1 + y * ZoneGridWidth] != null)
                        maps.AddRange(_ZoneGrid[x + 1 + y * ZoneGridWidth].GetMapsForRectangle(currentView));
                }

                if (currentView.Bottom >= tz.WorldCoordinate.Y + tz.ZoneHeight)
                {
                    if (_ZoneGrid[x + (y + 1) * ZoneGridWidth] != null)
                    {
                        maps.AddRange(_ZoneGrid[x + (y + 1) * ZoneGridWidth].GetMapsForRectangle(currentView));
                    }
                }

                if (currentView.Right >= tz.WorldCoordinate.X + tz.ZoneWidth && currentView.Bottom >= tz.WorldCoordinate.Y + tz.ZoneHeight)
                {
                    if (_ZoneGrid[x + 1 + (y + 1) * ZoneGridWidth] != null)
                    {
                        maps.AddRange(_ZoneGrid[x + 1 + (y + 1) * ZoneGridWidth].GetMapsForRectangle(currentView));
                    }
                }
            }

            return maps;
        }

        /// <summary>Loads a map from file into the zone</summary>
        /// <param name="tz">Zone the map lives in</param>
        /// <param name="MapPoint">Top Left corner of Map</param>
        public void LoadMap(TileZone tz, Point MapPoint)
        {
            //Get x,y coordinates for map
            int x = MapPoint.X;
            int y = MapPoint.Y;

            //Name of Zones Directory
            string strZonesDir = tz.WorldCoordinate.X.ToString() + "X" + tz.WorldCoordinate.Y.ToString();
            //Name of Map File
            string strMapFileName = MapPoint.X.ToString() + "X" + MapPoint.Y.ToString() + ".TileMap";
            //Get Map File
            FileInfo fiMap = new FileInfo(WorldFilePath + @"\Zones\" + strZonesDir + @"\" + strMapFileName);
            //Serialize utility to load maps
            SerializationUtilities.Utilities<Map> stMaps = new SerializationUtilities.Utilities<Map>();

            //Based on zones x,y coordinates calculate where in array this map belongs.
            //Subtract Zones X,Y from maps coordinate to get zone relevant coordinates
            x = x - tz.WorldCoordinate.X;
            y = y - tz.WorldCoordinate.Y;
            //Divide by 128 to calculate location in array for x,y
            x = x / 128;
            y = y / 128;
            //Load fine into appropriate location in array.
            tz.Maps[x + y * TileZone.MapGridWidth] = stMaps.ReadObjectFromFile(fiMap.Open(FileMode.Open), SerializationType.XML, CompressionType.SevenZip);
        }

        public Tile GetTileAtLocation(Point worldCoordinate, MapLayerTypes LayerType)
        {
            int zonex = worldCoordinate.X / (128 * TileZone.MapGridWidth);
            int zoney = worldCoordinate.Y / (128 * TileZone.MapGridHeight);

            int mapx = (worldCoordinate.X % (128 * TileZone.MapGridWidth)) / 128;
            int mapy = (worldCoordinate.Y % (128 * TileZone.MapGridHeight)) / 128;

            int x = worldCoordinate.X % 128;
            int y = worldCoordinate.Y % 128;

            if (_ZoneGrid[zonex + zoney * ZoneGridWidth] != null)
            {
                TileZone tz = _ZoneGrid[zonex + zoney * ZoneGridWidth];

                Map tm = tz.Maps[mapx + mapy * TileZone.MapGridWidth];

                if(tm == null)
                {
                    int mx = zonex + mapx * 128;
                    int my = zoney + mapy * 128;
                    LoadMap(tz, new Point(mx, my));

                    tm = tz.Maps[mapx + mapy * TileZone.MapGridWidth];
                }

                return tm.GetTileAtLocation(new Point(x, y), LayerType);
            }

            return Tile.NonExistant;
        }

        public bool IsTileWalkable(Point worldCoordinate)
        {
            //TODO: Fix Collision, pending GameObject upgrade completion.
            return true;

            //Tile mt = GetTileAtLocation(worldCoordinate, MapLayerTypes.GroundCollision);

            //if (mt.SpriteID.ID == -1)
            //{
            //    return true;
            //}
            //else
            //{
            //    return false;
            //}

        }

        public void SetTileAtLocation(Point worldCoordinate, MapLayerTypes LayerType, Tile setTile)
        {
            Map tm = GetMapAtLocation(worldCoordinate);

            if (tm != null)
            {
                tm.SetTileAtLocation(tm.GetMapCoordinatesFromWorldCoordinates(worldCoordinate),
                    LayerType, setTile);
            }
        }

        protected Map GetMapAtLocation(Point worldCoordinate)
        {
            int zonex = worldCoordinate.X / (128 * TileZone.MapGridWidth);
            int zoney = worldCoordinate.Y / (128 * TileZone.MapGridHeight);

            int mapx = (worldCoordinate.X % (128 * TileZone.MapGridWidth)) / 128;
            int mapy = (worldCoordinate.Y % (128 * TileZone.MapGridHeight)) / 128;

            int x = worldCoordinate.X % 128;
            int y = worldCoordinate.Y % 128;

            if (_ZoneGrid[zonex + zoney * ZoneGridWidth] != null)
            {
                TileZone tz = _ZoneGrid[zonex + zoney * ZoneGridWidth];
                if (tz.Maps[mapx + mapy * TileZone.MapGridWidth] != null)
                {
                    return tz.Maps[mapx + mapy * TileZone.MapGridWidth];
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }

        #region LoadWorld
        public static TileWorld LoadWorld(string WorldPath)
        {
            TileWorld World;
            string filepath = WorldPath + "\\World.tw";
            //Load world from xml
            SerializationUtilities.Utilities<TileWorld> Utilities = new SerializationUtilities.Utilities<TileWorld>();
            World = Utilities.ReadObjectFromFile(File.OpenRead(filepath), SerializationType.XML, CompressionType.SevenZip);
            //Lets us know where world is located;
            World.WorldFilePath = WorldPath;
            //Load Directory Info's
            LoadDirectoryInfos(World);
            //Load AutoTileInfo
            LoadAutoTileInfo(World);
            //Connect Zones to World
            foreach (TileZone tz in World.Zones)
            {
                if (tz != null)
                    tz.ParentWorld = World;
            }

            return World;
        }
        
        protected static void LoadAutoTileInfo(TileWorld world)
        {
            string LoadPath = System.IO.Path.Combine(world.diTextures.FullName, "AutoTileInfo.AutoTile");

            SerializationUtilities.Utilities<AutoTileInfoCollection> stAutoTiles = new SerializationUtilities.Utilities<AutoTileInfoCollection>();

            AutoTileInfoCollection.GlobalAutoTileSets = stAutoTiles.ReadObjectFromFile(LoadPath, SerializationType.XML, CompressionType.SevenZip);
        }

        protected static void LoadDirectoryInfos(TileWorld world)
        {
            //Load all directory info objects
            world.diWorld = new DirectoryInfo(world.WorldFilePath);
            //Zones folder
            world.diZones = new DirectoryInfo(world.diWorld.FullName + @"\Zones");
            //Textures Folder
            world.diTextures = new DirectoryInfo(world.diWorld.FullName + @"\Textures");
            //Terrain TileSets Folder
            world.diTerrain = new DirectoryInfo(world.diTextures.FullName + @"\Terrain");
            //Character TileSets Folder
            world.diCharacters = new DirectoryInfo(world.diTextures.FullName + @"\Characters");
        }
        #endregion

        #region SaveWorld
        public void SaveWorld()
        {
            //Create our file path
            string filepath = WorldFilePath + "\\World.tw";
            //if the file exists already delete it
            File.Delete(filepath);
            //Create the file, just cause we feel like doing it here so we can use OpenWrite down below
            SerializationUtilities.Utilities<TileWorld> Utilities = new SerializationUtilities.Utilities<TileWorld>();
            //Serialize, Compress to File
            Utilities.WriteObjectToFile(this, File.Create(filepath), SerializationType.XML, CompressionType.SevenZip);
            //Save zone Maps
            SaveZoneMaps();
            //Save AutoTileInfo's
            SaveAutoTileInfo();
        }

        protected void SaveAutoTileInfo()
        {
            string savePath = Path.Combine(diTextures.FullName, "AutoTileInfo.AutoTile");

            SerializationUtilities.Utilities<AutoTileInfoCollection> stAutoTiles = new SerializationUtilities.Utilities<AutoTileInfoCollection>();

            stAutoTiles.WriteObjectToFile(AutoTileInfoCollection.GlobalAutoTileSets, savePath, SerializationType.XML, CompressionType.SevenZip);
        }

        protected void SaveZoneMaps()
        {
            //For each zone check to see if a folder exists for it.
            foreach (TileZone tz in Zones)
            {
                if (tz != null)
                {
                    //Name of Zones Directory
                    string strZonesDir = tz.WorldCoordinate.X.ToString() + "X" + tz.WorldCoordinate.Y.ToString();
                    //Get Directory Info for it
                    DirectoryInfo diSpecificZone = new DirectoryInfo(diZones.FullName + @"\" + strZonesDir);
                    //If directory does not exist
                    if (!diSpecificZone.Exists)
                    {
                        diZones.CreateSubdirectory(strZonesDir);
                    }

                    SerializationUtilities.Utilities<Map> stMaps = new SerializationUtilities.Utilities<Map>();
                    //Save Maps to Zone Foler.  Only save maps that have changed?  Or save them all?  Save all for now, maybe add flag later.
                    foreach (Map tm in tz.Maps)
                    {
                        if (tm != null)
                        {
                            //Build Map File Path
                            string strMapPath = diSpecificZone.FullName + @"\" + tm.WorldX.ToString() + "X" + tm.WorldY.ToString() + ".TileMap";
                            //Create Map file, old file overwritten if it exists
                            File.Delete(strMapPath);
                            FileStream fs = File.Open(strMapPath, FileMode.Create);
                            //Serialize map to filestream
                            stMaps.WriteObjectToFile(tm, fs, SerializationType.XML, CompressionType.SevenZip);
                        }
                    }
                }
            }
        }
        #endregion

        #region NewWorld
        public static TileWorld NewWorld(string WorldPath)
        {
            TileWorld tw = new TileWorld();
            tw.WorldFilePath = WorldPath;

            //Create Directories
            CreateNewWorldDirectories(tw);

            return tw;
        }

        protected static void CreateNewWorldDirectories(TileWorld world)
        {
            //Create World Directories
            world.diWorld = new DirectoryInfo(world.WorldFilePath);
            //Zones folder
            world.diZones = world.diWorld.CreateSubdirectory("Zones");
            //Textures Folder
            world.diTextures = world.diWorld.CreateSubdirectory("Textures");
            //Terrain TileSets Folder
            world.diTerrain = world.diTextures.CreateSubdirectory("Terrain");
            //Create Terrain xml file
            TileSetDataSets tts = new TileSetDataSets();
            //Write to file
            tts.WriteXml(world.diTerrain.FullName + @"\TerrainTileSets.xml");
            //Character TileSets Folder
            world.diCharacters = world.diTextures.CreateSubdirectory("Characters");
        }
        #endregion
    }
}